﻿using System;
using System.Data.SqlClient;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Security.AccessControl;
using System.Threading;
using System.Windows.Forms;
using System.Xml;
using DeVierKoeden.Dynamicweb.CreateSite.Extensions;
using DeVierKoeden.Dynamicweb.CreateSite.Rizzo;
using Microsoft.VisualBasic.FileIO;
using Microsoft.Web.Administration;

namespace DeVierKoeden.Dynamicweb.CreateSite
{
  internal class SiteGenerator : IDisposable
  {
    private InstallSettings _installSettings;
    private ServerManager _serverManager;
    private MainForm _form;
    private const string GuruConnectionString = @"metadata=res://*/DataModel.GuruDataModel.csdl|res://*/DataModel.GuruDataModel.ssdl|res://*/DataModel.GuruDataModel.msl;provider=System.Data.SqlClient;provider connection string=""data source={0};initial catalog={1};integrated security=True;MultipleActiveResultSets=True;App=EntityFramework""";

    private void SaveChangesToIis(MainForm form)
    {
      form.LogProgress("Saving changes to IIS...");
      _serverManager.CommitChanges();
    }

    private static void CleanupTemp(ISettings settings)
    {
      string parentFolder = new FileInfo(settings.DatabaseBackupPath).DirectoryName;
      try
      {
        DeleteFile(settings.DatabaseBackupPath);
        DeleteFolder(parentFolder);
      }
      catch
      {
        MessageBox.Show($"Error deleting temp folder {parentFolder}.");
      }
    }

    /// <summary>
    /// Starting point for the application. Creates the site, sets up the database etc.
    /// </summary>
    /// <param name="form">A reference to the form that holds the user's selections.</param>
    /// <param name="rizzoForm"></param>
    /// <returns>True when the app was created successfully, false when the install settings are null which is the case when there are validation errors.</returns>
    internal bool CreateSite(MainForm form, ConfigureRizzoForm rizzoForm)
    {
      _form = form;
      _installSettings = SetupSettings(_form);
      if (_installSettings == null)
      {
        _form.LogProgress("Ready");
        return false;
      }

      _serverManager = new ServerManager();

      // Create a new instance of an IIS site and assign it a default port of 80.
      _form.LogProgress("Creating site...");
      Site site = CreateSite();

      // Add the host header (which is the site's name)
      _form.LogProgress("Setting bindings...");
      SetBindings(site);

      // Set up the Files virtual directory pointing to the physical Files folder on disk.
      _form.LogProgress("Adding VDir for /Files...");
      AddVirtualDirectory(site.Applications[0], "/Files", _installSettings.TargetFilesFolder);

      // If this is a custom solution, setup /Admin as a VD. Otherwise, /Admin is just a sub folder of the root site and there's no need for a VDir.
      if (_installSettings.SolutionType == SolutionType.Custom)
      {
        _form.LogProgress("Adding VDir for /Admin...");
        AddVirtualDirectory(site.Applications[0], "/Admin", _installSettings.IisAdminFolder);
      }

      // If necessary, add a new application pool. Assign the pool to our new site.
      _form.LogProgress("Setting up application pool...");
      SetUpAppPool(site.Applications[0]);

      SetupSiteToUseAppPoolIdentity(site.Name);

      // Must commit pending changes here so the site gets saved. We need this to set up a site specific 404 handler.
      SaveChangesToIis(form);

      // Register the 404 handler for the site at /Admin/Public/404.aspx
      _form.LogProgress("Registering 404...");
      Register404(_installSettings.SiteName);

      // Save changes to the 404 handler.
      SaveChangesToIis(form);

      // If this is a Guru site, try to update the connection string
      var config = _serverManager.GetWebConfiguration(_installSettings.SiteName, "/");
      ConfigurationSection connectionStringElement;
      try
      {
        connectionStringElement = config.GetSection("connectionStrings", "/");
        ConfigurationElementCollection connectionStrings = connectionStringElement.GetCollection();

        var connectionString = connectionStrings.FirstOrDefault(x => x.ElementTagName == "add" && x.RawAttributes.Any(a => a.Key.ToLower() == "name" && a.Value == "GuruEntities"));
        if (connectionString != null)
        {
          connectionString.Attributes["connectionstring"].Value = string.Format(GuruConnectionString, _installSettings.SqlServerInstance, _installSettings.DatabaseName);
          //Save changes to Guru connection string
          SaveChangesToIis(form);
        }
      }
      catch
      {
        MessageBox.Show("Warning! The web.config configuration is invalid. This may be because a feature from the web.config is unavailable on your computer. You will need to update the Guru connection string manually.");
      }

      // Start IIS by making a request. This ensures the App Pool account exists so we can use it in SQL Server.
      _form.LogProgress("Warming up site in IIS ...");
      string adminUrl = string.Format(Configuration.StartBrowserArguments, _installSettings.SiteName);
      var request = (HttpWebRequest)WebRequest.Create(adminUrl);
      try
      {
        request.GetResponse();
      }
      catch
      {
        // Ignore. This may fail if the application is custom and hasn't been created. All we care about is that IIS spins up.
      }

      // Creates a new database and restore the .bak file into this.
      _form.LogProgress("Restoring SQL Server database...");

      CreateAndRestoreDatabase(_installSettings);

      // Create a new SQL login named after the app pool and make it a user of the database.
      _form.LogProgress("Creating SQL login and user...");
      CreateUserAndLogin(_installSettings);

      // Add reader, writer and DDL permission. Add or remove roles here as needed.
      _form.LogProgress("Setting up SQL Server permissions...");
      AssignUserToRoles(_installSettings, "db_datareader", "db_datawriter", "db_ddladmin");

      // Do some housekeeping; get rid of stuff from the default database we don't need.
      _form.LogProgress("Cleaning up unneeded SQL Server objects...");
      DeleteUnnecessaryUsers(_installSettings, "DefaultEmptyDatasql", "DefaultEmptyData", "default8", "demo", "qa");

      if (_installSettings.ResetPassword)
      {
        // Assign the Administrator account a password. See app.config for the default password.
        _form.LogProgress("Resetting the Administrator password...");
        ResetAdminPassword(_installSettings);
      }

      if (_installSettings.DisableScheduledTasks)
      {
        _form.LogProgress("Disabling scheduled tasks...");
        DisableScheduledTasks(_installSettings);
      }

      if (_installSettings.RandomizeUserPasswords)
      {
        _form.LogProgress("Randomizing passwords");
        RandomizePasswords(_installSettings);
      }

      // Execute custom SQL to the target database.
      string sqlFile = Configuration.GetAppSetting("PostInstallSqlFixesFile");
      if (!string.IsNullOrEmpty(sqlFile))
      {
        _form.LogProgress("Executing custom SQL Scripts...");
        ExecuteCustomSql(sqlFile, _installSettings);
      }

      // Set up the database node of GlobalSettings.aspx to configure SQL Server. 
      _form.LogProgress("Updating GlobalSettings.aspx file...");
      UpdateGlobalSettingsFile(_installSettings);

      // Add a record to the hosts file. This associates the site's domain name with 127.0.0.1 so you can use it off-line.
      if (_installSettings.UpdateHostsFile)
      {
        _form.LogProgress("Updating Windows host file...");
        UpdateHostsFile();
      }

      if (rizzoForm != null && _form.CustomizeRizzo) // When null, no customizations have been set
      {
        _form.LogProgress("Updating Rizzo item types...");
        rizzoForm.SaveItemTypeFiles(_installSettings);
        rizzoForm.FixupDesignName(_installSettings);

        rizzoForm.DeleteDynamicwebOrdersIfChecked(_installSettings);
        rizzoForm.DeleteUnneededFoldersIfChecked(_installSettings);
        rizzoForm.DeleteModuleDataIfChecked(_installSettings);
        rizzoForm.DeleteStatsIfChecked(_installSettings);
        rizzoForm.CleanInstallIfChecked(_installSettings);
        rizzoForm.ShrinkDatabase(_installSettings);
        rizzoForm.ConfigureMiscellaneous(_installSettings);
      }

      // Clean up temp files.
      _form.LogProgress("Deleting old SQL Server .bak file...");
      CleanupTemp(_installSettings);
      // Reset the file set numbers to a low number so schema updates are applied
      ResetFileNumbers(_installSettings);

      UpdateBinFolder();
      UpdateWebConfig();

      AclFolders(_installSettings);
      _form.LogProgress("Configuring Filesystem permissions...");

      RestartApplicationPool(_installSettings);


      // Startup front end to get loading and caching going.
      _form.LogProgress("Warming up site...");
      string url = $"http://{_installSettings.SiteName}";
      request = (HttpWebRequest)WebRequest.Create(url);
      try
      {
        request.GetResponse();
      }
      catch
      {
        // Ignore. This may fail if the application is custom and hasn't been created. All we care about is that IIS spins up.
      }

      BuildIndex();

      if (_installSettings.AnonymizeData)
      {
        _form.LogProgress("Anonymizing data");
        var anonymizer = new DataAnonymization.DataAnonymizer(_installSettings, _form);
        anonymizer.AnonymizeData();
      }

      _form.LogProgress("Starting your browser; almost done...");
      StartBrowser(adminUrl);

      if (_installSettings.SolutionType == SolutionType.Custom && !_installSettings.HasCustomApplicationFolder)
      {
        MessageBox.Show(
          $"A new, empty Application folder has been created at {_installSettings.TargetFolder}. You should add a new Dynamicweb Visual Studio project to this folder. Until then, your site won't run correctly.");
      }

      _form.LogProgress("Done...");
      form.Dispose();
      return true;
    }


    internal bool UpdateVersion(MainForm form)
    {
      _form = form;
      _serverManager = new ServerManager();

      _form.LogProgress("Updating Dynamicweb version...");

      var site = form.SiteForVersionUpgrade;

      var rootFolder = site.GetRootPath();
      var appBin = Path.Combine(rootFolder, "bin");
      var adminFolderOld = site.GetAdminPath();

      var directoryInfo = new DirectoryInfo(adminFolderOld);
      if (directoryInfo.Parent?.Parent == null) // If any of these are null
      {
        throw new Exception(string.Format("Couldn't get DirectoryInfo for path {0} or for it's parent folders.", adminFolderOld));

      }
      var applicationsFolder = directoryInfo.Parent.Parent.FullName;

      var zipPath = form.DwZipSourceFileForVersionUpgrade;
      var appVersion = new DirectoryInfo(zipPath).Name;
      var adminFolderNew = Path.Combine(applicationsFolder, appVersion).Replace(".zip", "", StringComparison.CurrentCultureIgnoreCase);

      var adminFolderNewAdmin = Path.Combine(adminFolderNew, "Admin");
      var dwInstallRootFolder = Path.Combine(Configuration.GetAppSetting("StandardAppFolder"), Path.GetFileNameWithoutExtension(appVersion));

      if (!Directory.Exists(dwInstallRootFolder))
      {
        _form.LogProgress("Unzipping new application...");
        ZipFile.ExtractToDirectory(zipPath, dwInstallRootFolder);
      }
      _form.LogProgress("Remapping Admin path...");
      var sm = new ServerManager();
      var site2 = sm.Sites[site.Name];
      site2.Applications[0].VirtualDirectories["/admin"].PhysicalPath = adminFolderNewAdmin;
      sm.CommitChanges();

      var appName = site2.Applications[0].ApplicationPoolName;

      _form.LogProgress("Stopping site...");
      StopApplicationPool(appName);
      Thread.Sleep(1000);
      try
      {
        _form.LogProgress("Updating BIN folder...");
        CopyBinFiles(adminFolderNew, appBin);
      }
      catch
      {
        StopApplicationPool(appName);
        Thread.Sleep(1000);
        try
        {
          CopyBinFiles(adminFolderNew, appBin);
        }
        catch (Exception ex)
        {
          MessageBox.Show(
            "Updating bin folder failed. You should manually copy the files from the application's Bin folder to the site's Bin folder. Error message: " + ex.Message);
        }
      }

      _form.LogProgress("Starting site...");
      RestartApplicationPool(appName);

      _form.LogProgress("Starting up browser...");
      string adminUrl = string.Format(Configuration.StartBrowserArguments, site2.Name);
      StartBrowser(adminUrl);

      _form.LogProgress("Done");
      return true;
    }

    private static void CopyBinFiles(string source, string target)
    {
      foreach (var file in new DirectoryInfo(Path.Combine(source, "bin")).EnumerateFiles())
      {
        var destinationName = Path.Combine(target, file.Name);
        try
        {
          file.CopyTo(destinationName, true);
        }
        catch
        {
          MessageBox.Show(string.Format("Error copying file: {0}. You'll need to manually update this file.", destinationName));
        }
      }
    }

    private void BuildIndex()
    {
      const string indexUrl = "http://{0}/Admin/Api/repositories/build/Products/Products-index-index/{1}/Full%20Catalogue/";

      var index = new[] { "A", "B" };
      for (int i = 0; i < index.Length; i++)
      {
        string url = string.Format(indexUrl, _installSettings.SiteName, index[i]);
        var req = WebRequest.Create(url);

        req.Method = "POST";
        req.ContentType = "application/json";
        req.ContentLength = 0;
        try
        {
          var res = req.GetResponse();
          using (var reader = new StreamReader(res.GetResponseStream()))
          {
            reader.ReadToEnd();
          }
        }
        catch
        {
          // Ignore. This may fail if the application is custom and hasn't been created. All we care about is that IIS spins up.
        }
      }
    }

    private void UpdateWebConfig()
    {
      if (!_installSettings.HasCustomApplicationFolder)
      {
        return;
      }
      var mustWriteConfig = false;
      DirectoryInfo sourceConfigFolder;
      if (_installSettings.UseStandardWebConfig)
      {
        sourceConfigFolder = new DirectoryInfo(_installSettings.IisAdminFolder).Parent;
      }
      else
      {
        sourceConfigFolder = new DirectoryInfo(_installSettings.TargetApplicationFolder);
      }

      Debug.Assert(sourceConfigFolder != null, "sourceConfig != null");
      var source = Path.Combine(sourceConfigFolder.FullName, "web.config");
      var target = Path.Combine(_installSettings.TargetApplicationFolder, "web.config");

      if (_installSettings.UseStandardWebConfig)
      {
        File.Copy(source, target, true);
      }

      var doc = new XmlDocument();
      doc.Load(source);

      var names = new XmlNamespaceManager(doc.NameTable);
      names.AddNamespace("a", "urn:schemas-microsoft-com:asm.v1");

      var webGreaseNode = doc.SelectSingleNode("//configuration/runtime/a:assemblyBinding/a:dependentAssembly/a:assemblyIdentity[@name='WebGrease']", names);
      if (webGreaseNode == null)
      {
        var assemblyBinding = doc.SelectSingleNode("//configuration/runtime/a:assemblyBinding", names);
        var xfrag = doc.CreateDocumentFragment();
        xfrag.InnerXml = @"<dependentAssembly>
            <assemblyIdentity name=""WebGrease"" publicKeyToken=""31bf3856ad364e35"" culture=""neutral"" />
            <bindingRedirect oldVersion=""0.0.0.0-1.5.2.14234"" newVersion=""1.5.2.14234"" />
          </dependentAssembly>
            ";
        Debug.Assert(assemblyBinding != null, "assemblyBinding != null");
        assemblyBinding.AppendChild(xfrag);
        mustWriteConfig = true;
      }
      else
      {
        // Node exist. Make sure it has the correct version number
        Debug.Assert(webGreaseNode.ParentNode != null, "webGreaseNode.ParentNode != null");
        var bindingRedirect = webGreaseNode.ParentNode.SelectSingleNode("a:bindingRedirect", names);
        Debug.Assert(bindingRedirect != null, "bindingRedirect != null");
        var oldVersion = bindingRedirect.SelectSingleNode("@oldVersion");
        var newVersion = bindingRedirect.SelectSingleNode("@newVersion");
        Debug.Assert(oldVersion != null, "oldVersion != null");
        Debug.Assert(newVersion != null, "newVersion != null");
        if (oldVersion.Value != "0.0.0.0-1.5.2.14234")
        {
          oldVersion.Value = "0.0.0.0-1.5.2.14234";
          mustWriteConfig = true;
        }
        if (newVersion.Value != "1.5.2.14234")
        {
          newVersion.Value = "1.5.2.14234";
          mustWriteConfig = true;
        }
      }
      if (mustWriteConfig)
      {
        File.WriteAllText(target, doc.Beautify().Replace(" xmlns=\"\"", ""));
      }

      if (_installSettings.MajorAndMinorVersion >= 93)
      {
        var dotlessCoreNode = doc.SelectSingleNode("//configuration/runtime/a:assemblyBinding/a:dependentAssembly/a:assemblyIdentity[@name='dotless.Core']", names);
        if (dotlessCoreNode == null)
        {
          var assemblyBinding = doc.SelectSingleNode("//configuration/runtime/a:assemblyBinding", names);
          var xfrag = doc.CreateDocumentFragment();
          xfrag.InnerXml = @"<dependentAssembly>
            <assemblyIdentity name=""dotless.Core"" publicKeyToken=""96b446c9e63eae34"" culture=""neutral"" />
            <bindingRedirect oldVersion=""0.0.0.0-1.5.2.0"" newVersion=""1.5.2.0"" />
          </dependentAssembly>
            ";
          Debug.Assert(assemblyBinding != null, "assemblyBinding != null");
          assemblyBinding.AppendChild(xfrag);
          mustWriteConfig = true;
        }
        else
        {
          // Node exist. Make sure it has the correct version number
          Debug.Assert(dotlessCoreNode.ParentNode != null, "dotlessCoreNode.ParentNode != null");
          var bindingRedirect = dotlessCoreNode.ParentNode.SelectSingleNode("a:bindingRedirect", names);
          Debug.Assert(bindingRedirect != null, "bindingRedirect != null");
          var oldVersion = bindingRedirect.SelectSingleNode("@oldVersion");
          var newVersion = bindingRedirect.SelectSingleNode("@newVersion");
          Debug.Assert(oldVersion != null, "oldVersion != null");
          Debug.Assert(newVersion != null, "newVersion != null");
          if (oldVersion.Value != "0.0.0.0-1.5.2.0")
          {
            oldVersion.Value = "0.0.0.0-1.5.2.0";
            mustWriteConfig = true;
          }
          if (newVersion.Value != "1.5.2.0")
          {
            newVersion.Value = "1.5.2.0";
            mustWriteConfig = true;
          }
        }
        if (mustWriteConfig)
        {
          File.WriteAllText(target, doc.Beautify().Replace(" xmlns=\"\"", ""));
        }
      }
    }

    private void UpdateBinFolder()
    {
      if (_installSettings.HasCustomApplicationFolder)
      {
        var sourceBin = new DirectoryInfo(_installSettings.IisAdminFolder).Parent;
        Debug.Assert(sourceBin != null, "sourceBin != null");
        var source = new DirectoryInfo(Path.Combine(sourceBin.FullName, "bin"));
        var target = new DirectoryInfo(Path.Combine(_installSettings.TargetApplicationFolder, "bin"));
        // Go ahead and copy each file to the target directory
        foreach (var file in source.EnumerateFiles())
        {
          file.CopyTo(Path.Combine(target.FullName, file.Name), true);
        }
      }
    }

    private void RandomizePasswords(InstallSettings installSettings)
    {
      const string sql = "UPDATE AccessUser SET AccessUserPassword = NewID() WHERE AccessUserPassword IS NOT NULL AND AccessUserPassword <> '' AND AccessUserType > 3";
      Helpers.RunProcess("SQLCMD", $@"-S{installSettings.SqlServerInstance} -E -d""{installSettings.DatabaseName}"" -Q""{sql}""");
    }

    private void DisableScheduledTasks(InstallSettings installSettings)
    {
      const string sql = "UPDATE ScheduledTask SET TaskEnabled = 0";
      Helpers.RunProcess("SQLCMD", $@"-S{installSettings.SqlServerInstance} -E -d""{installSettings.DatabaseName}"" -Q""{sql}""");
    }

    private void SetupSiteToUseAppPoolIdentity(string siteName)
    {
      var config = _serverManager.GetApplicationHostConfiguration();

      var anonymousAuthenticationSection = config.GetSection("system.webServer/security/authentication/anonymousAuthentication", siteName);
      anonymousAuthenticationSection["userName"] = @"";
    }

    /// <summary>
    /// Sets permissions for the app pool account to the application, admin and Files folder
    /// </summary>
    private void AclFolders(ISettings settings)
    {
      Helpers.AclFolder(settings.TargetFilesFolder, FileSystemRights.Modify, settings.ApplicationPoolAccount);
      if (!string.IsNullOrWhiteSpace(settings.TargetApplicationFolder) && settings.SolutionType == SolutionType.Custom)
      {
        Helpers.AclFolder(settings.TargetApplicationFolder, FileSystemRights.Modify, settings.ApplicationPoolAccount);
      }
      Helpers.AclFolder(settings.DwInstallRootFolder, FileSystemRights.Read | FileSystemRights.ListDirectory | FileSystemRights.ReadAndExecute, settings.ApplicationPoolAccount);
    }

    private void ResetFileNumbers(ISettings settings)
    {
      File.WriteAllText(settings.TargetFilesFolder + "\\Update.xml", "601");
      File.WriteAllText(settings.TargetFilesFolder + "\\UpdateVersion_ecom.xml", "201");
    }

    private Site CreateSite()
    {
      return _serverManager.Sites.Add(_installSettings.SiteName, _installSettings.IisRootPath, 80);
    }

    private void SetBindings(Site site)
    {
      site.Bindings.Clear();
      site.Bindings.Add($"*:80:{_installSettings.SiteName}", "http");
    }

    private void AddVirtualDirectory(Microsoft.Web.Administration.Application application, string folderName, string location)
    {
      application.VirtualDirectories.Add(folderName, location);
    }

    private void SetUpAppPool(Microsoft.Web.Administration.Application application)
    {
      if (_serverManager.ApplicationPools.All(x => x.Name != _installSettings.ApplicationPoolName))
      {
        ApplicationPool appPool = _serverManager.ApplicationPools.Add(_installSettings.ApplicationPoolName);
        appPool.ManagedRuntimeVersion = "v4.0";
        appPool.ProcessModel.LoadUserProfile = true;
      }
      application.ApplicationPoolName = _installSettings.ApplicationPoolName;
    }

    private void RestartApplicationPool(ISettings settings)
    {
      RestartApplicationPool(settings.ApplicationPoolName);
    }

    private void RestartApplicationPool(string name)
    {
      ApplicationPool appPool = _serverManager.ApplicationPools.FirstOrDefault(x => x.Name == name);
      if (appPool != null)
      {
        if (appPool.State == ObjectState.Started)
        {
          appPool.Recycle();
        }
        else
        {
          if (appPool.State == ObjectState.Stopped)
          {
            appPool.Start();
          }
        }
      }
    }
    private void StopApplicationPool(string name)
    {
      var appPool = _serverManager.ApplicationPools.FirstOrDefault(x => x.Name == name);
      if (appPool != null)
      {
        if (appPool.State == ObjectState.Started)
        {
          appPool.Stop();
        }
      }
      else
      {
        MessageBox.Show(string.Format("Can't find app pool {0}", name));
      }
    }

    private void Register404(string siteName)
    {
      var config = _serverManager.GetWebConfiguration(siteName, "/");
      ConfigurationSection httpErrorsSection;
      try
      {
        httpErrorsSection = config.GetSection("system.webServer/httpErrors", "/");
      }
      catch
      {
        MessageBox.Show("Warning! The web.config configuration is invalid. This may be because a feature from the web.config is unavailable on your computer. Is URL Rewrite installed? You will need to set the 404 handler manually.");
        return;
      }

      ConfigurationElementCollection httpErrorsCollection = httpErrorsSection.GetCollection();

      var errorElement = httpErrorsCollection.FirstOrDefault(x => x.ElementTagName == "error" && x.Attributes.Any(a => a.Name == "statusCode" && a.Value.ToString() == "404"));
      if (errorElement == null)
      {
        httpErrorsCollection.GetAttribute("errorMode").Value = "Custom";
        errorElement = httpErrorsCollection.CreateElement("error");
        errorElement["statusCode"] = 404;
        errorElement["subStatusCode"] = -1;
        errorElement["path"] = @"/Admin/Public/404.aspx";
        errorElement["responseMode"] = @"ExecuteURL";
        httpErrorsCollection.Add(errorElement);
      }
    }

    private void CreateAndRestoreDatabase(ISettings settings)
    {
      Helpers.AclFile(settings.DatabaseBackupPath);

      string databaseLocation = string.Empty;
      string logLocation = string.Empty;

      //      string tempDatabase = Guid.NewGuid().ToString().Replace("{", "").Replace("}", "").Replace("-", "");

      if (settings.MustCreateDatabase)
      {
        // Create temp database and then query physical paths
        Helpers.RunProcess("SQLCMD", $@"-S{settings.SqlServerInstance} -E -Q""CREATE DATABASE [{settings.DatabaseName}]""");
      }

      string sql = "select physical_name AS PhysicalName from sys.database_files;";
      using (var sqlConnection = new SqlConnection(
        $"data source={settings.SqlServerInstance}; integrated security=true;Initial catalog={settings.DatabaseName}"))
      {
        using (var sqlCommand = new SqlCommand(sql, sqlConnection))
        {
          sqlConnection.Open();
          try
          {
            using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
            {
              while (sqlDataReader.Read())
              {
                if (sqlDataReader["PhysicalName"].ToString().ToLower().EndsWith(".mdf"))
                {
                  databaseLocation = sqlDataReader["PhysicalName"].ToString();
                }
                if (sqlDataReader["PhysicalName"].ToString().ToLower().EndsWith(".ldf"))
                {
                  logLocation = sqlDataReader["PhysicalName"].ToString();
                }
              }
              sqlDataReader.Close();
            }
          }
          catch (Exception ex)
          {
            MessageBox.Show($"Error retrieving locations: {ex.Message}.");
            throw;
          }
        }
        sqlConnection.Close();
      }

      Helpers.RunProcess("SQLCMD", $@"-S{settings.SqlServerInstance} -E -Q""ALTER DATABASE [{settings.DatabaseName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE""");

      // Remove temp database again; the restore command below creates it at the proper location. The Single User command removes all locks / connections so
      // we can delete the database.
      Helpers.RunProcess("SQLCMD", $@"-S{settings.SqlServerInstance} -E -Q""DROP DATABASE [{settings.DatabaseName}]""");
      string restoreCommand = $@"-S {settings.SqlServerInstance} -E -Q""RESTORE DATABASE [{settings.DatabaseName}] FROM  DISK = N'{settings.DatabaseBackupPath}' WITH  FILE = 1,  MOVE N'{settings.DatabasePhysicalName}' TO N'{databaseLocation}',  MOVE N'{settings.DatabaseLogName}' TO N'{logLocation}',  NOUNLOAD,  REPLACE,  STATS = 10""";
      Helpers.RunProcess("osql.exe", restoreCommand);
    }

    private void CreateUserAndLogin(ISettings settings)
    {
      string newLogin = string.Format(@"-S{2} -E -d""{0}"" -Q""IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE name = N'IIS APPPOOL\{1}') CREATE LOGIN [IIS APPPOOL\{1}] FROM WINDOWS WITH DEFAULT_DATABASE=[master]""", settings.DatabaseName, settings.ApplicationPoolName, settings.SqlServerInstance);
      Helpers.RunProcess("SQLCMD", newLogin);

      string newUser = string.Format(@"-S{2} -E -d""{0}"" -Q""CREATE USER [{1}] FOR LOGIN [IIS APPPOOL\{1}]""", settings.DatabaseName, settings.ApplicationPoolName, settings.SqlServerInstance);
      Helpers.RunProcess("SQLCMD", newUser);
    }

    private void AssignUserToRoles(ISettings settings, params string[] roleNames)
    {
      const string role = @"-S{3} -E -d ""{0}"" -Q""EXEC sp_addrolemember N'{1}', N'{2}'""";
      foreach (var roleName in roleNames)
      {
        Helpers.RunProcess("SQLCMD", string.Format(role, settings.DatabaseName, roleName, settings.ApplicationPoolName, settings.SqlServerInstance));
      }
    }

    private void DeleteUnnecessaryUsers(ISettings settings, params string[] userNames)
    {
      foreach (var userName in userNames)
      {
        Helpers.RunProcess("SQLCMD", string.Format(@"-S{0} -E -d""{1}"" -Q""IF EXISTS (SELECT * FROM sys.database_principals WHERE name = N'{2}') DROP USER [{2}]""", settings.SqlServerInstance, settings.DatabaseName, userName));
      }
    }

    private void UpdateGlobalSettingsFile(ISettings settings)
    {
      // Create an XML document instance, and load XML data.
      var doc = new XmlDocument();
      doc.Load(settings.TargetFilesFolder + @"\GlobalSettings.aspx");

      var node = doc.SelectSingleNode("//System/Database/Type");
      if (node == null)
      {
        MessageBox.Show($"Node \"Type\" not found in the <database> node of the GlobalSettings file.");
        return;
      }
      node.InnerText = "ms_sqlserver";

      node = doc.SelectSingleNode("//System/Database/Database");
      if (node == null)
      {
        MessageBox.Show($"Node \"Database\" not found in the <database> node of the GlobalSettings file.");
        return;
      }
      node.InnerText = settings.DatabaseName;

      node = doc.SelectSingleNode("//System/Database/SQLServer");
      if (node == null)
      {
        MessageBox.Show($"Node \"SQLServer\" not found in the <database> node of the GlobalSettings file.");
        return;
      }
      node.InnerText = settings.SqlServerInstance;

      node = doc.SelectSingleNode("//System/Database/IntegratedSecurity");
      if (node == null)
      {
        MessageBox.Show($"Node \"IntegratedSecurity\" not found in the <database> node of the GlobalSettings file.");
        return;
      }
      node.InnerText = "True";

      doc.Save(settings.TargetFilesFolder + @"\GlobalSettings.aspx");
    }

    private void ExecuteCustomSql(string sqlFile, ISettings settings)
    {
      Helpers.RunProcess("SQLCMD",
        $@"-S{settings.SqlServerInstance} -E -d""{settings.DatabaseName}"" -i""{sqlFile}""");
    }

    private void ResetAdminPassword(ISettings settings)
    {
      string sql = "UPDATE AccessUser SET AccessUserPassword = NewID(), AccessUserType = 0 WHERE AccessUserUserName = 'Administrator'";
      Helpers.RunProcess("SQLCMD", $@"-S{settings.SqlServerInstance} -E -d""{settings.DatabaseName}"" -Q""{sql}""");
      sql = "UPDATE AccessUser SET AccessUserPassword = NewID(), AccessUserType = 3 WHERE AccessUserUserName = 'Admin'";
      Helpers.RunProcess("SQLCMD", $@"-S{settings.SqlServerInstance} -E -d""{settings.DatabaseName}"" -Q""{sql}""");
    }

    private void UpdateHostsFile()
    {
      try
      {
        string current = File.ReadAllText(_installSettings.HostsFilePath);
        current += $"\r\n127.0.0.1\t{_installSettings.SiteName}";
        File.WriteAllText(_installSettings.HostsFilePath, current);
      }
      catch
      {
        MessageBox.Show(
          $"Error modifying hosts file at {_installSettings.HostsFilePath}. Make sure the file exists and you have permissions to edit it.");
      }
    }

    private static void StartBrowser(string adminUrl)
    {
      try
      {
        // Could fail when IE or FF is not your default browser.
        Process.Start(adminUrl);
      }
      catch
      {
        MessageBox.Show($"Your site is ready at {adminUrl}.");
      }
    }

    private static void DeleteFile(string fullPath)
    {
      try
      {
        if (File.Exists(fullPath))
        {
          File.Delete(fullPath);
        }
      }
      catch
      {
        MessageBox.Show($"Error deleting file {fullPath}.");
      }
    }
    private static void DeleteFolder(string fullPath)
    {
      try
      {
        if (Directory.Exists(fullPath))
        {
          Directory.Delete(fullPath);
        }
      }
      catch
      {
        MessageBox.Show($"Error deleting folder {fullPath}.");
      }
    }

    private static void ExtractCustomFileToSiteFolder(InstallSettings installSettings, string targetFolder)
    {
      string tempFolder = targetFolder + "\\Temp";
      ZipFile.ExtractToDirectory(installSettings.CustomZipSourceFile, tempFolder);
      if (installSettings.HasCustomFilesFolder)
      {
        if (Directory.Exists(installSettings.TargetFilesFolder))
        {
          Directory.Delete(installSettings.TargetFilesFolder, true);
        }
        new Microsoft.VisualBasic.Devices.Computer().FileSystem.MoveDirectory(installSettings.TargetFolder + "\\Temp\\Files\\", installSettings.TargetFilesFolder);
      }
      else
      {
        if (Directory.Exists(tempFolder + "\\Files\\"))
        {
          Directory.Delete(tempFolder + "\\Files\\", true);
        }
      }
      if (installSettings.HasCustomApplicationFolder)
      {
        if (Directory.Exists(installSettings.TargetApplicationFolder))
        {
          Directory.Delete(installSettings.TargetApplicationFolder, true);
        }
        new Microsoft.VisualBasic.Devices.Computer().FileSystem.MoveDirectory(installSettings.TargetFolder + "\\Temp\\Application\\", installSettings.TargetApplicationFolder);
      }
      else
      {
        if (Directory.Exists(tempFolder + "\\Application\\"))
        {
          Directory.Delete(tempFolder + "\\Application\\", true);
        }
      }
      if (installSettings.HasCustomOverrides)
      {
        new Microsoft.VisualBasic.Devices.Computer().FileSystem.MoveDirectory(installSettings.TargetFolder + "\\Temp\\", installSettings.TargetFolder);
      }
    }

    /// <summary>
    /// Gets the settings from the MainForm form that holds the user's selections and builds up an InstallSettings instance.
    /// </summary>
    /// <param name="form">The MainForm where the user picked the various installation settings.</param>
    /// <returns>An instance of InstallSettings when all settings are valid, or null otherwise.</returns>
    private static InstallSettings SetupSettings(MainForm form)
    {
      form.LogProgress("Configuring settings...");

      var installSettings = new InstallSettings { SiteName = form.SiteName };
      installSettings.TargetFolder = Path.Combine(Configuration.GetAppSetting("TargetFolder"), installSettings.SiteName);
      installSettings.TargetFilesFolder = Path.Combine(installSettings.TargetFolder, "Files");
      installSettings.TargetApplicationFolder = Path.Combine(installSettings.TargetFolder, "Application");

      installSettings.SolutionType = form.SolutionType;
      installSettings.SiteType = form.SiteType;
      installSettings.UseDefaultAppPool = form.UseDefaultAppPool;
      installSettings.ResetPassword = form.ResetPassword;
      installSettings.AnonymizeData = form.AnonymizeData;
      installSettings.AnonymizationSuffix = form.AnonymizationSuffix;
      installSettings.AnonymizationMethod = form.AnonymizationMethod;
      installSettings.RandomizeUserPasswords = form.RandomizeUserPasswords;
      installSettings.UseStandardWebConfig = form.UseStandardWebConfig;
      installSettings.DisableScheduledTasks = form.DisableScheduledTasks;

      installSettings.HasCustomApplicationFolder = form.HasCustomApplicationFolder;
      installSettings.HasCustomFilesFolder = form.HasCustomFilesFolder;
      installSettings.HasCustomSqlDatabase = form.HasCustomSqlDatabase;
      if (installSettings.HasCustomSqlDatabase)
      {
        installSettings.CustomSqlDatabase = form.CustomSqlDatabase;
      }
      if (installSettings.HasCustomOverrides && form.CustomSource == CustomSource.ZipFile)
      {
        installSettings.CustomZipSourceFile = form.CustomSourceLocation;
      }
      if (installSettings.HasCustomOverrides && form.CustomSource == CustomSource.Folder)
      {
        installSettings.CustomSourceFolder = form.CustomSourceLocation;
      }

      switch (form.SiteType)
      {
        case SiteType.Blank:
          installSettings.DataFile = Configuration.GetAppSetting("DefaultEmptyDataFile");
          break;
        case SiteType.Existing:
          installSettings.DataFile = installSettings.CustomZipSourceFile;
          break;
      }

      installSettings.DwZipSourceFile = form.DwZipSourceFile;
      installSettings.DwInstallRootFolder = Path.Combine(Configuration.GetAppSetting("StandardAppFolder"), Path.GetFileNameWithoutExtension(form.DwZipSourceFile));

      installSettings.SqlServerInstance = form.SqlServerInstance;
      installSettings.IisAdminFolder = Path.Combine(installSettings.DwInstallRootFolder, "Admin");
      installSettings.IisRootPath = installSettings.DwInstallRootFolder;

      if (form.SolutionType == SolutionType.Custom)
      {
        installSettings.IisRootPath = installSettings.TargetApplicationFolder;
      }

      switch (form.SiteType)
      {
        case SiteType.Blank:
          installSettings.DatabaseBackupPath = $"{installSettings.TargetFolder}\\DefaultEmptyData.bak";
          break;
      }

      installSettings.ApplicationPoolName = installSettings.UseDefaultAppPool ? "Dynamicweb 8 Sites" : installSettings.SiteName;

      if (form.UpdateHostsFile)
      {
        installSettings.UpdateHostsFile = true;
        installSettings.HostsFilePath = Configuration.GetAppSetting("HostsFile");
      }

      bool hasError = false;
      string errors = string.Empty;
      foreach (string item in installSettings.ValidateProperties())
      {
        errors += $"{item}{Environment.NewLine}";
        hasError = true;
      }

      if (hasError)
      {
        MessageBox.Show(errors);
        return null;
      }

      if (installSettings.SiteType != SiteType.Existing)
      {
        form.LogProgress("Unzipping files and database...");
        UnzipFiles(installSettings);
      }

      UnzipApplication(installSettings);
      EnsureApplicationFolder(installSettings);

      if (installSettings.HasCustomFilesFolder && Directory.Exists(installSettings.TargetFilesFolder))
      {
        Directory.Delete(installSettings.TargetFilesFolder, true); // Delete the Files folder from the default as we'll supply a custom one from the ZIP file.
      }

      if (installSettings.HasCustomSqlDatabase)
      {
        if (File.Exists(installSettings.DatabaseBackupPath))
        {
          DeleteFile(installSettings.DatabaseBackupPath);
        }
        installSettings.DatabaseBackupPath = $"{installSettings.TargetFolder}\\{installSettings.CustomSqlDatabase}";
      }
      if (installSettings.HasCustomOverrides && form.CustomSource == CustomSource.ZipFile)
      {
        form.LogProgress("Unzipping custom ZIP file...");
        ExtractCustomFileToSiteFolder(installSettings, installSettings.TargetFolder);
      }
      if (installSettings.HasCustomOverrides && form.CustomSource == CustomSource.Folder)
      {
        new Microsoft.VisualBasic.Devices.Computer().FileSystem.CopyDirectory(installSettings.CustomSourceFolder, installSettings.TargetFolder);
      }
      installSettings.DatabaseName = installSettings.SiteName;

      MoveSqlBackupFileToTempFolder(installSettings);

      if (!string.IsNullOrWhiteSpace(installSettings.DatabaseBackupPath))
      {
        int backupVersion = Helpers.GetSqlServerDatabaseVersion(installSettings.DatabaseBackupPath, installSettings);
        int sqlVersion = Helpers.GetSqlServerDatabaseVersion(installSettings.SqlServerInstance, installSettings.SiteName);

        if (backupVersion > sqlVersion)
        {
          MessageBox.Show($"You cannot restore this database against this version of SQL Server. The backup is version {backupVersion}. This server supports version {sqlVersion} and earlier. Select a version of SQL Server in the SQL Server Instance field that matches your backup version.");
          CleanupTemp(installSettings);
          return null;
        }
      }

      if (!GetSqlNamesAndValidateInstance(form, installSettings))
      {
        CleanupTemp(installSettings);
        return null;
      }


      return installSettings;
    }

    private static void MoveSqlBackupFileToTempFolder(InstallSettings installSettings)
    {
      string currentLocation = installSettings.DatabaseBackupPath;
      string fileName = Path.GetFileName(currentLocation);
      string tempFolder = $"{Path.GetTempPath()}{Guid.NewGuid()}";
      Directory.CreateDirectory(tempFolder);
      Helpers.AclFolder(tempFolder, FileSystemRights.Modify, "Everyone");
      string newFullPath = Path.Combine(tempFolder, fileName);
      new Microsoft.VisualBasic.Devices.Computer().FileSystem.MoveFile(currentLocation, newFullPath);
      Helpers.AclFile(newFullPath); // Move retains original permissions.
      installSettings.DatabaseBackupPath = newFullPath;
    }

    /// <summary>
    /// Gets the logical names for the database and log file from the physical backup file. 
    /// </summary>
    /// <param name="form"></param>
    /// <param name="installSettings"></param>
    /// <returns></returns>
    private static bool GetSqlNamesAndValidateInstance(MainForm form, ISettings installSettings)
    {
      form.LogProgress("Finding SQL Server data and log file names...");
      string sql = $"RESTORE FILELISTONLY FROM DISK = N'{installSettings.DatabaseBackupPath}'";
      using (var sqlConnection = new SqlConnection(
        $"data source={installSettings.SqlServerInstance}; integrated security=true;"))
      {
        using (var sqlCommand = new SqlCommand(sql, sqlConnection))
        {
          sqlConnection.Open();
          try
          {
            using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
            {
              while (sqlDataReader.Read())
              {
                if (sqlDataReader["Type"].ToString() == "D")
                {
                  installSettings.DatabasePhysicalName = sqlDataReader["LogicalName"].ToString();
                }
                if (sqlDataReader["Type"].ToString() == "L")
                {
                  installSettings.DatabaseLogName = sqlDataReader["LogicalName"].ToString();
                }
              }
              sqlDataReader.Close();
            }
          }
          catch (Exception ex)
          {
            if (ex.HResult == -2146232060)
            {
              MessageBox.Show($"Error restoring database. It may be from an incompatible version of SQL Server. The error message is: {ex.Message}. Canceled site creation.");
              return false;
            }
            MessageBox.Show($"Error restoring database. The error message is: {ex.Message}. Canceled site creation.");
            return false;
          }
        }
      }
      return true;
    }

    private static void UnzipFiles(InstallSettings installSettings)
    {
      ZipFile.ExtractToDirectory(installSettings.DataFile, installSettings.TargetFolder);
    }

    private static void UnzipApplication(InstallSettings installSettings)
    {
      if (!Directory.Exists(installSettings.DwInstallRootFolder))
      {
        ZipFile.ExtractToDirectory(installSettings.DwZipSourceFile, installSettings.DwInstallRootFolder);
      }
    }

    private static void EnsureApplicationFolder(InstallSettings installSettings)
    {
      if (installSettings.SolutionType == SolutionType.Custom && !installSettings.HasCustomApplicationFolder)
      {
        if (!Directory.Exists(installSettings.TargetApplicationFolder))
        {
          Directory.CreateDirectory(installSettings.TargetApplicationFolder);
        }
        File.WriteAllText(Path.Combine(installSettings.TargetApplicationFolder, "Readme.txt"), "Place your Dynamicweb custom solution here. Use the Dynamicweb templates for Visual Studio to jump start your development.");
      }
    }

    public void Dispose()
    {
      Dispose(true);
      GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
      if (disposing)
      {
        if (_serverManager != null)
        {
          _serverManager.Dispose();
          _serverManager = null;
        }
      }
    }

    public bool RefreshSite(Site site, string zipFile, bool refreshApplication, bool refreshFiles, bool refreshDatabase, string database, bool renameFileFolderInsteadOfDeletingIt, MainForm mainForm)
    {
      var formForRefresh = mainForm;

      var refreshSettings = SetupRefreshSettings(site, zipFile, refreshApplication, refreshFiles, refreshDatabase, database, mainForm);

      if (refreshSettings == null)
      {
        formForRefresh.LogProgress("Ready");
        return false;
      }

      _serverManager = new ServerManager();

      if (refreshSettings.ShouldRefreshApplication || refreshSettings.ShouldRefreshFilesFolder)
      {
        RestartApplicationPool(refreshSettings);
      }
      if (refreshSettings.ShouldRefreshFilesFolder)
      {
        RefreshFileFolder(refreshSettings, renameFileFolderInsteadOfDeletingIt);
      }
      if (refreshSettings.ShouldRefreshApplication)
      {
        RefreshApplicationFolder(refreshSettings, renameFileFolderInsteadOfDeletingIt);
      }

      // Register the 404 handler for the site at /Admin/Public/404.aspx
      formForRefresh.LogProgress("Registering 404...");
      Register404(refreshSettings.SiteName);

      // Save changes to the 404 handler.
      SaveChangesToIis(formForRefresh);

      // Start IIS by making a request. This ensures the App Pool account exists so we can use it in SQL Server.
      formForRefresh.LogProgress("Warming up site in IIS ...");
      string adminUrl = string.Format(Configuration.StartBrowserArguments, refreshSettings.SiteName);
      var request = (HttpWebRequest)WebRequest.Create(adminUrl);
      try
      {
        request.GetResponse();
      }
      catch
      {
        // Ignore. This may fail if the application is custom and hasn't been created. All we care about is that IIS spins up.
      }

      // Creates a new database and restore the .bak file into this.
      formForRefresh.LogProgress("Restoring SQL Server database...");

      if (refreshDatabase)
      {
        CreateAndRestoreDatabase(refreshSettings);
        // Create a new SQL login named after the app pool and make it a user of the database.
        formForRefresh.LogProgress("Creating SQL login and user...");
        CreateUserAndLogin(refreshSettings);

        // Add reader, writer and DDL permission. Add or remove roles here as needed.
        formForRefresh.LogProgress("Setting up SQL Server permissions...");
        AssignUserToRoles(refreshSettings, "db_datareader", "db_datawriter", "db_ddladmin");

        // Do some housekeeping; get rid of stuff from the default database we don't need.
        formForRefresh.LogProgress("Cleaning up unneeded SQL Server objects...");
        DeleteUnnecessaryUsers(refreshSettings, "DefaultEmptyDatasql", "DefaultEmptyData", "default8", "demo", "qa", "muppet.dw-demo.com", "rizzo.dw-demo.com");
      }

      if (refreshFiles || refreshDatabase)
      {
        // Either (or both) has changed, so we need to sync them again.
        // Set up the database node of GlobalSettings.aspx to configure SQL Server. 
        formForRefresh.LogProgress("Updating GlobalSettings.aspx file...");
        UpdateGlobalSettingsFile(refreshSettings);
      }

      // Clean up temp files.
      formForRefresh.LogProgress("Deleting old SQL Server .bak file...");
      string parentFolder = new FileInfo(refreshSettings.DatabaseBackupPath).DirectoryName;
      DeleteFile(refreshSettings.DatabaseBackupPath);
      new Microsoft.VisualBasic.Devices.Computer().FileSystem.DeleteDirectory(parentFolder, DeleteDirectoryOption.DeleteAllContents);

      // Reset the file set numbers to 1 so schema updates are applied
      ResetFileNumbers(refreshSettings);

      formForRefresh.LogProgress("Configuring Filesystem permissions...");
      AclFolders(refreshSettings);

      RestartApplicationPool(refreshSettings);

      // Startup front end to get loading and caching going.
      formForRefresh.LogProgress("Warming up site...");
      string url = $"http://{refreshSettings.SiteName}";
      request = (HttpWebRequest)WebRequest.Create(url);
      try
      {
        request.GetResponse();
      }
      catch
      {
        // Ignore. This may fail if the application is custom and hasn't been created. All we care about is that IIS spins up.
      }

      formForRefresh.LogProgress("Starting your browser; almost done...");
      StartBrowser(adminUrl);

      formForRefresh.LogProgress("Done...");
      return true;
    }

    private void RefreshApplicationFolder(RefreshSettings refreshSettings, bool renameFileFolderInsteadOfDeletingIt)
    {
      if (!refreshSettings.IsCustomSolution)
      {
        MessageBox.Show("Can't assign Application folder to non-custom solution");
        return;
      }
      if (refreshSettings.ShouldRefreshApplication)
      {
        RefreshFolder(refreshSettings.TargetApplicationFolder, Path.Combine(refreshSettings.ExtractedSourceFolder, "Application"), renameFileFolderInsteadOfDeletingIt);
      }
    }

    private void RefreshFileFolder(RefreshSettings refreshSettings, bool renameFileFolderInsteadOfDeletingIt)
    {
      RefreshFolder(refreshSettings.TargetFilesFolder, Path.Combine(refreshSettings.ExtractedSourceFolder, "Files"), renameFileFolderInsteadOfDeletingIt);
    }

    private void RefreshFolder(string oldFolder, string newFolder, bool renameFileFolderInsteadOfDeletingIt)
    {
      if (renameFileFolderInsteadOfDeletingIt)
      {
        Directory.Move(oldFolder, GetUniqueOldName(oldFolder));
      }
      else
      {
        new Microsoft.VisualBasic.Devices.Computer().FileSystem.DeleteDirectory(oldFolder, DeleteDirectoryOption.DeleteAllContents);
      }
      new Microsoft.VisualBasic.Devices.Computer().FileSystem.CopyDirectory(newFolder, oldFolder);
    }

    private string GetUniqueOldName(string folder)
    {
      string newName = folder + "_Old";
      if (!Directory.Exists(newName))
      {
        return newName;
      }
      int i = 1;
      string nameToTest = string.Format("{0}_{1}", newName, i.ToString().PadLeft(2, '0'));
      while (Directory.Exists(nameToTest))
      {
        i++;
        nameToTest = string.Format("{0}_{1}", newName, i.ToString().PadLeft(2, '0'));
      }
      return nameToTest;
    }

    private RefreshSettings SetupRefreshSettings(Site site, string zipFile, bool refreshApplication, bool refreshFiles, bool refreshDatabase, string database, MainForm mainForm)
    {
      mainForm.LogProgress("Configuring settings...");

      var refreshSettings = new RefreshSettings
      {
        SiteName = site.Name,
        TargetFilesFolder = site.GetFilesPath(),
        ShouldRefreshApplication = refreshApplication,
        ShouldRefreshFilesFolder = refreshFiles,
        ShouldRefreshSqlDatabase = refreshDatabase,
        DataFile = zipFile,
        SqlServerInstance = site.GetSqlServerInstance(),
        IisAdminFolder = site.GetAdminPath(),
        ApplicationPoolName = site.GetApplicationPoolName(),
        CustomSqlDatabase = database,
        DatabaseName = site.GetDatabaseName(),
        IsCustomSolution = site.IsCustomSolution()
      };

      refreshSettings.SolutionType = refreshSettings.IsCustomSolution ? SolutionType.Custom : SolutionType.Standard;
      string tempFolder = string.Format("{0}{1}", Path.GetTempPath(), Guid.NewGuid());
      Directory.CreateDirectory(tempFolder);
      Helpers.AclFolder(tempFolder, FileSystemRights.Modify, "Everyone");
      ZipFile.ExtractToDirectory(refreshSettings.DataFile, tempFolder);

      refreshSettings.ExtractedSourceFolder = tempFolder;

      refreshSettings.DwInstallRootFolder = site.IsCustomSolution() ? site.GetAdminPath() : site.GetRootPath();
      refreshSettings.TargetApplicationFolder = site.IsCustomSolution() ? site.GetRootPath() : "";

      refreshSettings.DatabaseBackupPath = $"{tempFolder}\\{refreshSettings.CustomSqlDatabase}";

      if (!string.IsNullOrWhiteSpace(refreshSettings.DatabaseBackupPath))
      {
        int backupVersion = Helpers.GetSqlServerDatabaseVersion(refreshSettings.DatabaseBackupPath, refreshSettings);
        int sqlVersion = Helpers.GetSqlServerDatabaseVersion(refreshSettings.SqlServerInstance, refreshSettings.SiteName);

        if (backupVersion > sqlVersion)
        {
          MessageBox.Show($"You cannot restore this database against this version of SQL Server. The backup is version {backupVersion}. This server supports version {sqlVersion} and earlier. Select a version of SQL Server in the SQL Server Instance field that matches your backup version.");
          CleanupTemp(refreshSettings);
          return null;
        }
      }

      if (!GetSqlNamesAndValidateInstance(mainForm, refreshSettings))
      {
        CleanupTemp(refreshSettings);
        return null;
      }
      return refreshSettings;
    }
  }
}